﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Threading;

namespace Com.PhilChuang.Utils.Mobile
{
	public class AppFileWp7Impl : IAppFile
	{
		public bool DirectoryExists (String path)
		{
			path.ThrowIfNull ("path");
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				return storage.DirectoryExists (path);
			}
		}

		public void CreateDirectory (String path)
		{
			path.ThrowIfNull ("path");
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				storage.CreateDirectory (path);
			}
		}

		public void CreateDirectoryIfNotExists (String path)
		{
			path.ThrowIfNull ("path");
			if (!DirectoryExists (path))
				CreateDirectory (path);
		}

		public void DeleteDirectory (String path)
		{
			path.ThrowIfNull ("path");
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				storage.DeleteDirectory (path);
			}
		}

		public void DeleteDirectory (String path, bool recursive)
		{
			if (!recursive)
			{
				DeleteDirectory (path);
				return;
			}

			foreach (var dir in GetDirectoriesIn (path))
			{
				DeleteDirectory (dir);
			}

			foreach (var file in GetFilesIn (path))
			{
				FileDelete (file);
			}

			DeleteDirectory (path);
		}

		public IEnumerable<String> GetFilesIn (String path)
		{
			path.ThrowIfNull ("path");
			if (!DirectoryExists (path))
				throw new DirectoryNotFoundException (path);
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				foreach (var file in storage.GetFileNames (Path.Combine (path, "*")))
				{
					yield return Path.Combine (path, file);
				}
			}
			yield break;
		}

		public IEnumerable<String> GetDirectoriesIn (String path)
		{
			path.ThrowIfNull ("path");
			if (!DirectoryExists (path))
				throw new DirectoryNotFoundException (path);
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				foreach (var dir in storage.GetDirectoryNames (Path.Combine (path, "*")))
				{
					yield return Path.Combine (path, dir);
				}
			}
			yield break;
		}

		public void FileDelete (String path)
		{
			path.ThrowIfNull ("path");
			using (var fileLock = GetFileLock (path))
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				if (storage.FileExists (path))
					storage.DeleteFile (path);
			}
		}

		public bool FileExists (String path)
		{
			path.ThrowIfNull ("path");
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				return storage.FileExists (path);
			}
		}

		public void FileOpenRead (String path, Action<Stream> doStreamWork)
		{
			path.ThrowIfNull ("path");
			doStreamWork.ThrowIfNull ("doStreamWork");
			using (var fileLock = GetFileLock (path))
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			using (var stream = storage.OpenFile (path, FileMode.Open))
			{
				doStreamWork (stream);
			}
		}

		public void FileCreate (String path, Action<Stream> doStreamWork)
		{
			path.ThrowIfNull ("path");
			doStreamWork.ThrowIfNull ("doStreamWork");
			using (var fileLock = GetFileLock (path))
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				var dir = Path.GetDirectoryName (path);
				if (!storage.DirectoryExists (dir))
					storage.CreateDirectory (dir);
				using (var stream = storage.OpenFile (path, FileMode.Create))
				{
					doStreamWork (stream);
				}
			}
		}

		public void FileAppendText (String path, Action<StreamWriter> doStreamWriterWork)
		{
			path.ThrowIfNull ("path");
			doStreamWriterWork.ThrowIfNull ("doStreamWriterWork");
			using (var fileLock = GetFileLock (path))
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			using (var stream = storage.OpenFile (path, FileMode.Append))
			using (var sw = new StreamWriter (stream))
			{
				doStreamWriterWork (sw);
			}
		}

		public long GetFreeSpace ()
		{
			using (var storage = IsolatedStorageFile.GetUserStoreForApplication ())
			{
				return storage.AvailableFreeSpace;
			}
		}

		private readonly Object lock_m_FileLockMap = new Object ();
		private readonly Dictionary<String, ManualResetEvent> m_FileLockMap = new Dictionary<string, ManualResetEvent> ();
		private FileLock GetFileLock (String path)
		{
			path.ThrowIfNull ("path");

			var pathUpper = path.ToUpperInvariant ();

			do
			{
				ManualResetEvent mre;
				if (m_FileLockMap.TryGetValue (pathUpper, out mre))
					mre.WaitOne ();

				lock (lock_m_FileLockMap)
				{
					if (m_FileLockMap.ContainsKey (pathUpper))
						continue;

					var mre2 = new ManualResetEvent (false);

					m_FileLockMap[pathUpper] = mre2;

					return new FileLock (path,
					                     () => {
					                     	lock (lock_m_FileLockMap)
					                     	{
					                     		m_FileLockMap.Remove (pathUpper);
					                     		mre2.Set ();
					                     	}
					                     });
				}
			} while (true);
		}

		private class FileLock : IDisposable
		{
			public String Path { get; private set; }

			private readonly Action m_OnDispose;

			public FileLock (String path, Action onDispose)
			{
				path.ThrowIfNull ("path");
				onDispose.ThrowIfNull ("onDispose");

				Path = path;
				m_OnDispose = onDispose;
			}

			public void Dispose ()
			{
				m_OnDispose ();
			}
		}
	}
}
